@icon("../icons/state_chart_debugger.svg")
extends Control

#region 属性
const DebuggerHistory = preload("res://addons/State_Chart/util/debugger_history.gd")
@export var init_node_to_watch:NodePath
@export var maximum_lines:int = 300
@export var ignore_events:bool = false
@export var ignore_state_changes:bool = false
@export var ignore_transitions:bool = false
@onready var _tree:Tree = %Tree
@onready var _history_edit:TextEdit = %HistoryEdit
var _chart:StateChart
var _root:Node
var _connected_states:Array[State] = []
var _connected_transitions:Dictionary = {}
var _history:DebuggerHistory = null
#endregion
#region 字段
@export var enabled:bool = true:
	set(value):
		enabled = value
		if not Engine.is_editor_hint():
			_setup_processing(enabled)
#endregion
#region 信号
#endregion

#region 基类方法
#endregion
#region 公共方法
func add_history_entry(text:String):
	_history.add_history_entry(Engine.get_process_frames(),text)
func debug_node(root:Node) -> bool:
	if not enabled:
		return false
	_root = root
	_disconnect_all_signals()
	var success = _debug_node(root)
	if not success:
		push_error("找不到StartChart，无法Debug")
		_setup_processing(false)
		_chart = null
	else:
		_connect_all_signals()
		_clear_history()
		_setup_processing(true)
	return success
#endregion
#region 私有方法
func _debug_node(root:Node) -> bool:
	if not is_instance_valid(root):
		return false
	if root is StateChart:
		_chart = root
		return true
	for child in root.get_children():
		if _debug_node(child):
			return true
	return false
func _setup_processing(enabled:bool):
	process_mode = Node.PROCESS_MODE_ALWAYS if enabled else Node.PROCESS_MODE_DISABLED
	visible = enabled
func _disconnect_all_signals():
	if is_instance_valid(_chart):
		if not ignore_events:
			_chart.event_received.disconnect(_on_event_received)
	for state in _connected_states:
		if is_instance_valid(state):
			state.state_entered.disconnect(_on_state_entered)
			state.state_exited.disconnect(_on_state_exited)
	for transition in _connected_transitions.keys():
		if is_instance_valid(transition):
			transition.taken.disconnect(_connected_transitions.get(transition))
func _connect_all_signals():
	_connected_states.clear()
	_connected_transitions.clear()
	if not is_instance_valid(_chart):
		return
	_chart.event_received.connect(_on_event_received)
	for child in _chart.get_children():
		if child is State:
			_connect_signals(child)
func _connect_signals(state:State):
	state.state_entered.connect(_on_state_entered.bind(state))
	state.state_exited.connect(_on_state_exited.bind(state))
	_connected_states.append(state)
	for child in state.get_children():
		if child is State:
			_connect_signals(child)
		if child is Transition:
			var callable = _on_before_transition.bind(child,state)
			child.taken.connect(callable)
			_connected_transitions[child] = callable
func _collect_active_states(root:Node,parent:TreeItem):
	for child in root.get_children():
		if child is State:
			if child.active:
				var state_item:TreeItem = _tree.create_item(parent)
				state_item.set_text(0,child.name)
				if is_instance_valid(child.pending_transition):
					var transition_item:TreeItem = state_item.create_child()
					transition_item.set_text(0, ">> %s (%.2f)" % [child.pending_transition.name, child.pending_transition_remaining_delay])
				_collect_active_states(child,state_item)
func _clear_history():
	_history_edit.text = ""
	_history.clear()
func _on_before_transition(transition:Transition,source:State):
	if ignore_transitions:
		return
	_history.add_transition(Engine.get_process_frames(), transition.name, _chart.get_path_to(source), _chart.get_path_to(transition.resolve_target()))
func _on_event_received(event:StringName):
	if ignore_events:
		return
	_history.add_event(Engine.get_process_frames(),event)
func _on_state_entered(state:State):
	if ignore_state_changes:
		return
	_history.add_state_entered(Engine.get_process_frames(),state.name)
func _on_state_exited(state:State):
	if ignore_state_changes:
		return
	_history.add_state_exited(Engine.get_process_frames(),state.name)
func _on_timer_timeout():
	if not _history_edit.visible or not _history.dirty:
		return
	_history_edit.text = _history.get_history_text()
	_history_edit.scroll_vertical = _history_edit.get_line_count() - 1
func _on_ignore_events_checkbox_toggled(button_pressed):
	ignore_events = button_pressed
func _on_ignore_state_changes_checkbox_toggled(button_pressed):
	ignore_state_changes = button_pressed
func _on_ignore_transitions_checkbox_toggled(button_pressed):
	ignore_transitions = button_pressed
#endregion
#region 生命周期函数
func _ready() -> void:
	process_mode = Node.PROCESS_MODE_ALWAYS
	
	_history = DebuggerHistory.new(maximum_lines)
	%CopyToClipboardButton.pressed.connect(func():DisplayServer.clipboard_set(_history_edit.text))
	%ClearButton.pressed.connect(_clear_history)
	
	var to_watch = get_node_or_null(init_node_to_watch)
	if is_instance_valid(to_watch):
		debug_node(to_watch)
	
	%IgnoreEventsCheckbox.set_pressed_no_signal(ignore_events)
	%IgnoreStateChangesCheckbox.set_pressed_no_signal(ignore_state_changes)
	%IgnoreTransitionsCheckbox.set_pressed_no_signal(ignore_transitions)
func _process(delta: float) -> void:
	_tree.clear()
	if not is_instance_valid(_chart):
		return
	var root = _tree.create_item()
	root.set_text(0,_root.name)
	_collect_active_states(_chart,root)
	var items = _chart.expression_properties.keys()
	if items.size() <= 0:
		return
	items.sort()
	var properties_root = root.create_child()
	properties_root.set_text(0,"< Expression properties >")
	for item in items:
		var value = str(_chart.expression_properties.get(item))
		var property_line = properties_root.create_child()
		property_line.set_text(0, "%s = %s" % [item,value])
#endregion
